/*
 * Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.data.contacts.v3;

import com.google.api.client.googleapis.GoogleUrl;
import com.google.api.client.util.DateTime;
import com.google.api.data.AbstractAtomService;
import com.google.api.data.LoggingUtils;
import com.google.api.data.contacts.v3.ContactsUrl.OrderBy;
import com.google.api.data.contacts.v3.ContactsUrl.SortOrder;
import com.google.api.data.contacts.v3.model.Contact;
import com.google.api.data.contacts.v3.model.ContactGroupsList;
import com.google.api.data.contacts.v3.model.ContactsList;
import com.google.api.data.contacts.v3.model.Relation;
import com.google.api.data.gdata.v2.model.Name;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Constants for the Google Contacts Data API.
 * 
 * @since 2.2
 * @author Nicolas Garnier
 */
public class ContactsService extends AbstractAtomService {

  // ============= Constructors ================

  /**
   * Constructs a Google Contacts API service.
   * 
   * @param applicationName The application Name
   */
  public ContactsService(String applicationName) {
    super(applicationName, ContactsApiInfo.NAMESPACE_DICTIONARY);
  }

  // =============== Setting up Service Information ================

  @Override
  protected String getDefaultApiVersion() {
    return ContactsApiInfo.VERSION;
  }

  @Override
  protected String getClientLoginServiceCode() {
    return ContactsApiInfo.AUTH_TOKEN_TYPE;
  }

  @Override
  protected String getServiceAuthenticationScope() {
    return ContactsApiInfo.AUTH_SCOPE;
  }

  // ============= API Requests Methods ================

  /**
   * Returns the list of Contacts, Shared Contacts or User Profiles available at
   * the given Feed URL.
   * 
   * @param url The URL of the Contacts Feed
   * @return The List of contacts available at the given Feed URL
   * @throws IOException
   */
  public ContactsList getContacts(GoogleUrl url) throws IOException {
    return super.executeGet(url, null, false, ContactsList.class);
  }

  /**
   * Returns the list of Personal Contacts of the authenticated account.
   * 
   * @return The List of personal contacts of the authenticated account
   * @throws IOException
   */
  public ContactsList getContacts() throws IOException {
    return super
        .executeGet(UrlFactory.getDefaultContactsFeedUrl(), null, false, ContactsList.class);
  }

  /**
   * Returns the list of Personal Contacts of the given account.
   * 
   * @param account The account of which to get the Contacts
   * @return The List of personal contacts of the given account
   * @throws IOException
   */
  public ContactsList getContacts(String account) throws IOException {
    return super
        .executeGet(UrlFactory.getContactsFeedUrl(account), null, false, ContactsList.class);
  }

  /**
   * Returns the list of Personal Contacts of the given account.
   * 
   * @param account The account of which to get the Contacts
   * @param maxResults The maximum number of entries to return. If you want to
   *          receive all of the contacts, rather than only the default maximum,
   *          you can specify a very large number for max-results.
   * @param startIndex The 1-based index of the first result to be retrieved
   *          (for paging).
   * @param updatedMin The inclusive lower bound on entry update dates.
   * @param orderBy Sorting criterion.
   * @param sortOrder Sorting order direction.
   * @param showDeleted Include deleted contacts in the returned contacts feed.
   *          Deleted contacts are shown as entries that contain nothing but an
   *          <atom:id> element and a <gd:deleted> element. (Google usually
   *          retains placeholders for deleted contacts for 30 days after
   *          deletion; during that time, you can request the placeholders using
   *          the showdeleted query parameter.) Valid values are true or false.
   *          When the server decides it cannot guarantee that it still has
   *          information about all deleted contacts pertinent to the query,
   *          then it's behavior depends on the value of the requirealldeleted
   *          query parameter.
   * @param requireAllDeleted Only relevant if showdeleted and updated-min are
   *          also provided. It dictates the behavior of the server in case it
   *          detects that placeholders of some entries deleted since the point
   *          in time specified as updated-min may have been lost. If
   *          requirealldeleted is false, the server simply returns all the
   *          placeholders it still knows about. If true, the server returns the
   *          410 HTTP response code. The default value is false.
   * @param group Constrains the results to only the contacts belonging to the
   *          group specified. Value of this parameter specifies group ID.
   * @return The List of Contacts matching the criteria.
   * @throws IOException
   */
  public ContactsList getContacts(String account, Integer maxResults, Integer startIndex,
      DateTime updatedMin, OrderBy orderBy, SortOrder sortOrder, Boolean showDeleted,
      Boolean requireAllDeleted, String group) throws IOException {
    ContactsUrl contactsUrl = UrlFactory.getContactsFeedUrl(account);
    contactsUrl.maxResults = maxResults;
    contactsUrl.startIndex = startIndex;
    contactsUrl.updatedMin = updatedMin;
    contactsUrl.orderBy = orderBy;
    contactsUrl.sortOrder = sortOrder;
    contactsUrl.showDeleted = showDeleted;
    contactsUrl.requireAllDeleted = requireAllDeleted;
    contactsUrl.group = group;
    return super.executeGet(contactsUrl, null, false, ContactsList.class);
  }

  /**
   * Returns the list of Shared Contacts of the given account.
   * 
   * @param domain The domain of which to get the Shared Contacts.
   * @return The List of shared contacts of the given domain.
   * @throws IOException
   */
  public ContactsList getSharedContacts(String domain) throws IOException {
    return super.executeGet(UrlFactory.getSharedContactsFeedUrl(domain), null, false,
        ContactsList.class);
  }

  /**
   * Returns the list of Shared Contacts of the given account.
   * 
   * @param domain The domain of which to get the Shared Contacts
   * @param maxResults The maximum number of entries to return. If you want to
   *          receive all of the contacts, rather than only the default maximum,
   *          you can specify a very large number for max-results.
   * @param startIndex The 1-based index of the first result to be retrieved
   *          (for paging).
   * @param updatedMin The inclusive lower bound on entry update dates.
   * @param orderBy Sorting criterion.
   * @param sortOrder Sorting order direction.
   * @param showDeleted Include deleted contacts in the returned contacts feed.
   *          Deleted contacts are shown as entries that contain nothing but an
   *          <atom:id> element and a <gd:deleted> element. (Google usually
   *          retains placeholders for deleted contacts for 30 days after
   *          deletion; during that time, you can request the placeholders using
   *          the showdeleted query parameter.) Valid values are true or false.
   *          When the server decides it cannot guarantee that it still has
   *          information about all deleted contacts pertinent to the query,
   *          then it's behavior depends on the value of the requirealldeleted
   *          query parameter.
   * @param requireAllDeleted Only relevant if showdeleted and updated-min are
   *          also provided. It dictates the behavior of the server in case it
   *          detects that placeholders of some entries deleted since the point
   *          in time specified as updated-min may have been lost. If
   *          requirealldeleted is false, the server simply returns all the
   *          placeholders it still knows about. If true, the server returns the
   *          410 HTTP response code. The default value is false.
   * @return The List of Shared Contacts matching the criteria.
   * @throws IOException
   */
  public ContactsList getSharedContacts(String domain, Integer maxResults, Integer startIndex,
      DateTime updatedMin, OrderBy orderBy, SortOrder sortOrder, Boolean showDeleted,
      Boolean requireAllDeleted) throws IOException {
    ContactsUrl contactsUrl = UrlFactory.getSharedContactsFeedUrl(domain);
    contactsUrl.maxResults = maxResults;
    contactsUrl.startIndex = startIndex;
    contactsUrl.updatedMin = updatedMin;
    contactsUrl.orderBy = orderBy;
    contactsUrl.sortOrder = sortOrder;
    contactsUrl.showDeleted = showDeleted;
    contactsUrl.requireAllDeleted = requireAllDeleted;
    return super.executeGet(contactsUrl, null, false, ContactsList.class);
  }

  /**
   * Returns the list of User Profiles of the given domain.
   * 
   * @param domain The domain of which to get the User Profiles.
   * @return The List of User Profiles matching the criteria.
   * @throws IOException
   */
  public ContactsList getUserProfiles(String domain) throws IOException {
    UserProfilesUrl userProfileUrl = UrlFactory.getDomainUserProfilesFeedUrl(domain);
    return super.executeGet(userProfileUrl, null, false, ContactsList.class);
  }

  /**
   * Returns the list of User Profiles of the given domain.
   * 
   * @param domain The domain of which to get the User Profiles.
   * @param maxResults The maximum number of entries to return.
   * @param startKey Opaque key of the first element to retrieve. Present in the
   *          next link of an earlier request, if further pages of response are
   *          available.
   * @return The List of User Profiles matching the criteria.
   * @throws IOException
   */
  public ContactsList getUserProfiles(String domain, Integer maxResults, String startKey)
      throws IOException {
    UserProfilesUrl userProfileUrl = UrlFactory.getDomainUserProfilesFeedUrl(domain);
    userProfileUrl.maxResults = maxResults;
    userProfileUrl.startKey = startKey;
    return super.executeGet(userProfileUrl, null, false, ContactsList.class);
  }

  /**
   * Returns the updated version of {@code contactsEntry} from the server or
   * simply {@code contactsEntry} if it is already up to date.
   * 
   * @param contact The contact to check the updated version of
   * @return The update version of the given entry if different or returns the
   *         {@code contactsEntry} if it is up to date.
   * @throws IOException
   */
  public Contact getUpdatedContact(Contact contact) throws IOException {
    GoogleUrl url = new GoogleUrl(contact.getSelfLink());
    Contact newContactsEntry = super.executeGet(url, contact.etag, false, Contact.class);
    if (newContactsEntry == null) {
      return contact;
    } else {
      return newContactsEntry;
    }
  }

  /**
   * Returns the Contacts, Shared Contacts or User Profiles available at the
   * given URL. This should be a Self Link of an entry.
   * 
   * @param url The URL of the Contacts
   * @return The List of contacts available at the given Feed URL
   * @throws IOException
   */
  public Contact getContact(String url) throws IOException {
    return super.executeGet(new GoogleUrl(url), null, false, Contact.class);
  }

  // ========================== Insert / POST ========================

  /**
   * Insert the given contact to the given account's contact list.
   * 
   * @param entry the contact to insert
   * @param account The contacts account to insert to
   * @return The inserted Contacts
   * @throws IOException
   */
  public Contact addContact(Contact contact, String account) throws IOException {
    return super.executeInsert(contact, UrlFactory.getContactsFeedUrl(account));
  }

  /**
   * Insert the given contact to the default logged-in account's contact list.
   * 
   * @param contact the contact to insert
   * @return The inserted Contacts
   * @throws IOException
   */
  public Contact addContact(Contact contact) throws IOException {
    return super.executeInsert(contact, UrlFactory.getDefaultContactsFeedUrl());
  }

  /**
   * Insert the given shared contact to the given domain's shared contacts list.
   * 
   * @param entry the contact to insert
   * @param account The contacts account to insert to
   * @return The inserted Contacts
   * @throws IOException
   */
  public Contact addSharedContact(Contact contact, String domain) throws IOException {
    return super.executeInsert(contact, UrlFactory.getSharedContactsFeedUrl(domain));
  }

  // ========================== Save / PUT ========================

  /**
   * Saves the given existing Contact / Shared Contacts / Profile. If you want
   * to override any intermediate changes make sure you change the etag of the
   * contact to '*'.
   * 
   * @param contact the existing Contact / Shared Contacts / Profile to save
   * @return The saved version of the contact
   * @throws IOException
   */
  public Contact saveContact(Contact contact) throws IOException {
    return super.executeUpdate(contact);
  }

  // ========================== Batch ========================

  /**
   * Executes the contact batch operation list on the currently (default)
   * authenticated account. The batch fields need to be filled for each Contact
   * object.
   * 
   * @param contacts The contacts batch operations to execute
   * @return The batch feed results of the contacts batch operations
   * @throws IOException
   */
  public ContactsList executeContactBatchOperations(ContactsList contacts) throws IOException {
    return super.executeBatch(contacts, UrlFactory.getDefaultContactsBatchFeedUrl());
  }

  /**
   * Executes the contact batch operation list on the given account. The batch
   * fields need to be filled for each Contact object.
   * 
   * @param contacts the contact batch operations to execute
   * @param account the account on which to execute the batch query
   * @return The batch feed results of the contacts batch operations
   * @throws IOException
   */
  public ContactGroupsList executeContactBatchOperations(ContactGroupsList contacts, String account)
      throws IOException {
    return super.executeBatch(contacts, UrlFactory.getContactsBatchFeedUrl(account));
  }

  /**
   * Executes the shared contact batch operation list on the given domain. The
   * batch fields need to be filled for each Contact object.
   * 
   * @param contacts the shared contact batch operations to execute
   * @param domain the domain on which to execute the batch query
   * @return The batch feed results of the shared contacts batch operations
   * @throws IOException
   */
  public ContactGroupsList executeSharedContactBatchOperations(ContactGroupsList contacts,
      String domain) throws IOException {
    return super.executeBatch(contacts, UrlFactory.getSharedContactsBatchFeedUrl(domain));
  }

  /**
   * Executes the profiles batch operation list on the given domain. The batch
   * fields need to be filled for each Contact object.
   * 
   * @param contacts the shared contact batch operations to execute
   * @param domain the domain on which to execute the batch query
   * @return The batch feed results of the profiles batch operations
   * @throws IOException
   */
  public ContactGroupsList executeProfilesBatchOperations(ContactGroupsList contacts, String domain)
      throws IOException {
    return super.executeBatch(contacts, UrlFactory.getDomainUserProfilesBatchFeedUrl(domain));
  }

  // ========================== Main for tests ========================

  /**
   * Main for testing
   * 
   * @param args
   * @throws IOException
   */
  public static void main(String[] args) throws IOException {
    LoggingUtils.enableLogging();
    ContactsService cs = new ContactsService("myTestApp");
    cs.setClientLoginCredentials("test@gmail.com", "test");
    
    Contact contact = new Contact();
    contact.name = new Name();
    contact.name.fullName = "aaaaaa";
    contact.relation = new ArrayList<Relation>();
    contact.relation.add(new Relation());
    contact.relation.get(0).type = Relation.TYPE_ASSISTANT;
    contact = cs.addContact(contact);
  }
}
